﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TSTree.fuctions
{
   public class CachedBlockFile : BlockFile
    {
        private const int FREE = 0;      //...
        private const int USED = 1;        //
        private const int FIXED = 2;        //

        private int ptr; //current position in cache
        private int cachesize;
        private int[] cache_cont; // array of the indices of blocks that are in cache
        private int[] fuf_cont; //indicator array that shows whether one cache block is free, used or fixed
        byte[][] cache;
        public CachedBlockFile(String name, int blength, int csize)
            : base(name, blength)
        {
            // initialize blockfile


            ptr = 0;
            if (csize >= 0)
                cachesize = csize;
            else
            {
                Console.WriteLine("CachedBlockFile.CachedBlockFile: neg. Cachesize");
                Environment.Exit(0);
            }
            cache_cont = new int[cachesize];
            fuf_cont = new int[cachesize];

            for (int i = 0; i < cachesize; i++)
            {
                cache_cont[i] = 0;
                fuf_cont[i] = FREE;
            }

            cache = new byte[cachesize][]; //Changed
            for (int i = 0; i < cachesize; i++)
                cache[i] = new byte[getBlocklength()];
        }

        // returns the index of the next available block in cache
        private int next()
        {
            int ret_val, tmp;

            if (cachesize == 0)
                return -1;
            else if (fuf_cont[ptr] == FREE) // if the next position is unused
            {
                ret_val = ptr++;
                if (ptr == cachesize)
                    ptr = 0;
                return ret_val;
            }
            else
            {
                tmp = (ptr + 1) % cachesize;
                //if(tmp==cachesize)
                //    tmp=0;
                while (tmp != ptr && fuf_cont[tmp] != FREE) //find the first free block
                    if (++tmp == cachesize)
                        tmp = 0;
                if (ptr == tmp) //failed to find a free block
                {
                    if (fuf_cont[ptr] == FIXED) //if ptr is fixed block find the first unfixed(used)
                    {
                        tmp = (ptr + 1) % cachesize;
                        //if(tmp==cachesize)
                        //    tmp=0;
                        while (tmp != ptr && fuf_cont[tmp] != USED)
                            if (++tmp == cachesize)
                                tmp = 0;
                        if (ptr == tmp) // all blocks are fixed. failure :(
                            return -1;
                        else
                            ptr = tmp; //save what we found to ptr
                    }
                    // write block to be freed back to file
                    base.writeBlock(cache[ptr], cache_cont[ptr] - 1);
                    fuf_cont[ptr] = FREE;
                    ret_val = ptr++;
                    if (ptr == cachesize)
                        ptr = 0;
                    return ret_val;
                }
                else
                    return tmp;
            }
        }

        // returns the index of the specified block in cache if exists
        private int inCache(int index)
        {
            int i;

            for (i = 0; i < cachesize; i++)
                if (cache_cont[i] == index && fuf_cont[i] != FREE)
                    return i;
            return -1;
        }

        //public override Boolean readBlock(byte[] block, int index)
        //{
        //    int c_ind;

        //    index++;
        //    if (index <= getNumOfBlocks() && index > 0)
        //    {
        //        if ((c_ind = inCache(index)) >= 0)
        //        // the block is in cache
        //        {
        //            int blclth = getBlocklength();
        //            for (int i = 0; i < blclth; i++)
        //                block[i] = cache[c_ind][i];
        //        }
        //        else
        //        // the block is not in cache
        //        {
        //            // find next free cache block or free one used
        //            c_ind = next();
        //            if (c_ind >= 0)
        //            {
        //                base.readBlock(cache[c_ind], index - 1);
        //                cache_cont[c_ind] = index;
        //                fuf_cont[c_ind] = USED;
        //                int blclth = getBlocklength();
        //                for (int i = 0; i < blclth; i++)
        //                    block[i] = cache[c_ind][i];
        //            }
        //            else
        //                base.readBlock(block, index - 1);
        //        }
        //        return true;
        //    }
        //    else
        //        return false;
        //}

        //public override Boolean writeBlock(byte[] block, int index)
        //{
        //    int c_ind;

        //    index++;
        //    if (index <= getNumOfBlocks() && index > 0)
        //    {
        //        c_ind = inCache(index);
        //        if (c_ind >= 0)
        //        {
        //            int blclth = getBlocklength();
        //            for (int i = 0; i < blclth; i++)
        //                cache[c_ind][i] = block[i];
        //        }
        //        else
        //        {
        //            c_ind = next();
        //            if (c_ind >= 0)
        //            {
        //                int blclth = getBlocklength();
        //                for (int i = 0; i < blclth; i++)
        //                    cache[c_ind][i] = block[i];
        //                cache_cont[c_ind] = index;
        //                fuf_cont[c_ind] = USED;
        //            }
        //            else
        //                base.writeBlock(block, index - 1);
        //        }
        //        return true;
        //    }
        //    else
        //        return false;
        //}

        public Boolean fixBlock(int index)
        {
            int c_ind;

            index++;
            if (index <= getNumOfBlocks() && index > 0)
            {
                if ((c_ind = inCache(index)) >= 0)
                    fuf_cont[c_ind] = FIXED;
                else
                    if ((c_ind = next()) >= 0)
                    {
                        base.readBlock(cache[c_ind], index - 1);
                        cache_cont[c_ind] = index;
                        fuf_cont[c_ind] = FIXED;
                    }
                    else
                        return false;
                return true;
            }
            else
                return false;
        }

        public Boolean unfixBlock(int index)
        {
            int i;

            i = 0;
            index++;
            if (index <= getNumOfBlocks() && index > 0)
            {
                while (i < cachesize && (cache_cont[i] != index || fuf_cont[i] == FREE))
                    i++;
                if (i != cachesize)
                    fuf_cont[i] = USED;
                return true;
            }
            else
                return false;
        }

        public void unfixAll()
        {
            int i;

            for (i = 0; i < cachesize; i++)
                if (fuf_cont[i] == FIXED)
                    fuf_cont[i] = USED;
        }

        public void setCachesize(int size)
        {
            int i;

            if (size >= 0)
            {
                ptr = 0;
                flush();
                cachesize = size;
                cache_cont = new int[cachesize];
                fuf_cont = new int[cachesize];  //Problems
                for (i = 0; i < cachesize; i++)
                {
                    cache_cont[i] = 0;
                    fuf_cont[i] = FREE;
                }

                cache = new byte[cachesize][];
                for (i = 0; i < cachesize; i++)
                    cache[i] = new byte[getBlocklength()];
            }
            else
            {
                Console.WriteLine("CacheBlockFile.set_cachesize: neg. cachesize");
                Environment.Exit(0);
            }
        }

        public void flush()
        {
            int i;

            for (i = 0; i < cachesize; i++)
                if (fuf_cont[i] != FREE)
                    base.writeBlock(cache[i], cache_cont[i] - 1);
        }
    }
}
